<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas 截图</title>

</head>

<body>
    <div>
        <input type="file">
        <button class="confirm">放入选中截图</button>
        <button class="download">下载选中截图</button>
    </div>
    <canvas id="cvs"></canvas>

    <div class="shot-img-area">
    </div>

    <script type="text/javascript">
        const MASK_OPACITY = .7

        class imgShotCvs {
            constructor(cvs) {
                this.cvs = cvs
                this.ctx = cvs.getContext('2d', { willReadFrequently: true })

                this.initState()
                this.bindEvent()
            }

            initState() {
                this.stPos = []
                this.endPos = []

                // 拖动截图区域大小
                this.shotWidth = 0
                this.shotHeight = 0

                // 填充图片高度 也是`canvas`高度
                this.width = 0
                this.height = 0
                // 填充的图片
                this.img = null
                // 截图区域的图片数据
                this.shotImgData = []
                // 偏移值
                const { left, top } = this.cvs.getBoundingClientRect()
                this.left = left
                this.top = top
            }

            setSize(w, h) {
                this.cvs.width = w
                this.cvs.height = h

                this.width = w
                this.height = h
            }

            setImg(img) {
                this.img = img
                this.ctx.drawImage(img, 0, 0)
            }

            bindEvent() {
                this.cvs.addEventListener('mousedown', this.onMouseDown)
            }

            onMouseDown = (e) => {
                this.stPos = [e.pageX - this.left, e.pageY - this.top]

                this.cvs.addEventListener('mousemove', this.onMouseMove)
                this.cvs.addEventListener('mouseup', this.onMouseUp)
            }

            onMouseMove = (e) => {
                this.endPos = [e.pageX - this.left, e.pageY - this.top]
                const [stX, stY] = this.stPos,
                    [endX, endY] = this.endPos

                // 记录 `终点 - 起点` 得到宽高
                this.shotWidth = endX - stX
                this.shotHeight = endY - stY

                this.clear()
                this.drawMask()
                this.drawSreenShot()
            }

            onMouseUp = () => {
                this.setShotImgData()
                this.cvs.removeEventListener('mousemove', this.onMouseMove)
                this.cvs.removeEventListener('mouseup', this.onMouseUp)
            }

            clear() {
                this.ctx.clearRect(0, 0, this.width, this.height)
            }

            drawMask() {
                this.ctx.fillStyle = `rgba(0, 0, 0, ${MASK_OPACITY})`
                this.ctx.fillRect(0, 0, this.width, this.height)
            }

            drawSreenShot() {
                // 擦除区域模式
                this.ctx.globalCompositeOperation = 'destination-out'
                // 从鼠标点击起点开始画 
                this.ctx.fillRect(...this.stPos, this.shotWidth, this.shotHeight)

                // 往擦除区域填充
                this.ctx.globalCompositeOperation = 'destination-over'
                this.setImg(this.img)
            }

            setShotImgData() {
                if (this.shotWidth === 0 || this.shotHeight === 0) {
                    return
                }
                this.shotImgData = this.ctx.getImageData(...this.stPos, this.shotWidth, this.shotHeight)
            }

            getShotImg() {
                const cvs = document.createElement('canvas'),
                    ctx = cvs.getContext('2d')

                cvs.width = this.shotWidth
                cvs.height = this.shotHeight
                ctx.putImageData(this.shotImgData, 0, 0)
                return cvs
            }
        }


        const inp = document.querySelector('input'),
            shotImgArea = document.querySelector('.shot-img-area'),
            confirm = document.querySelector('.confirm'),
            download = document.querySelector('.download'),
            cvs = document.getElementById('cvs'),
            imgShot = new imgShotCvs(cvs)

        bindEvent()

        function bindEvent() {
            inp.onchange = async function () {
                const src = await getBase64(this.files[0]),
                    img = await getImg(src),
                    { width, height } = img

                imgShot.setSize(width, height)
                imgShot.setImg(img)
                imgShot.drawMask()
            }

            confirm.onclick = function () {
                shotImgArea.querySelector('canvas')?.remove()

                const cvs = imgShot.getShotImg()
                shotImgArea.appendChild(cvs)
            }

            download.onclick = function () {
                const cvs = imgShot.getShotImg()
                cvs.toBlob(blob => {
                    const a = document.createElement('a'),
                        url = URL.createObjectURL(blob)

                    a.href = url
                    a.download = Date.now()
                    a.click()
                })
            }
        }

        function getBase64(blob) {
            const fr = new FileReader()
            fr.readAsDataURL(blob)

            return new Promise((resolve) => {
                fr.onload = function () {
                    resolve(this.result)
                }
            })
        }

        function getImg(src) {
            const img = new Image()
            img.src = src
            return new Promise((resolve) => {
                img.onload = function () {
                    resolve(img)
                }
            })
        }

    </script>
</body>

</html>